package com.dps.recommend.third;

import com.alibaba.fastjson.JSON;
import com.dps.metrics.processor.CustomizedMetricsProcessor;
import com.dps.recommend.beans.DpsRequest;
import com.dps.recommend.beans.Road;
import com.google.common.annotations.GwtIncompatible;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import lombok.Data;
import lombok.NonNull;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.dao.DataAccessException;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.connection.RedisStringCommands;
import org.springframework.data.redis.connection.StringRedisConnection;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.types.Expiration;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;

import java.io.PrintWriter;
import java.io.Serializable;
import java.io.StringWriter;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.regex.Matcher;
import java.util.regex.Pattern;


@Slf4j
@Data
public class DpsRedisCache implements Serializable {

    protected StringRedisTemplate stringRedisTemplate;

    public DpsRedisCache() {
    }

    public DpsRedisCache(StringRedisTemplate stringRedisTemplate) {
        this.stringRedisTemplate = stringRedisTemplate;
    }

    public StringRedisTemplate stringRedisTemplate() {
        return this.stringRedisTemplate;
    }

    private String getDpsRedisKey(DpsRequest dpsRequest) {
        StringBuffer stringBuffer = new StringBuffer();
        stringBuffer.append("dps");
        if (!Objects.isNull(dpsRequest) && !Objects.isNull(dpsRequest.getLayer()) && !Objects.isNull(dpsRequest.getRoad())) {
            stringBuffer.append("_sence_redis");
        } else {
            stringBuffer.append("_general_redis");
        }
        return stringBuffer.toString();
    }

    private Map<String, String> getTsLayerTagMap(DpsRequest dpsRequest, String operType) {
        Map<String, String> tagMapper = new HashMap<>();
        if (!Objects.isNull(dpsRequest) && !Objects.isNull(dpsRequest.getLayer()) && !Objects.isNull(dpsRequest.getRoad())) {
            Road road = dpsRequest.getRoad();
            String sence = dpsRequest.getRequest().getSence();
            String layerName = dpsRequest.getLayer().getLayerName();
            tagMapper.put("sence", StringUtils.isEmpty(sence) ? "" : sence);
            tagMapper.put("layer", StringUtils.isEmpty(layerName) ? "" : layerName);
            tagMapper.put("model", Objects.isNull(road) ? "" : road.getModel() + "_" + road.getRoadIndex());
        }
        tagMapper.put("operType", StringUtils.isEmpty(operType) ? "" : operType);
        return tagMapper;
    }


    private static Pattern p = Pattern.compile("\t|\r|\n");

    @GwtIncompatible
    private static String getStackTraceAsString(Throwable throwable) {
        StringWriter stringWriter = new StringWriter();
        throwable.printStackTrace(new PrintWriter(stringWriter));
        String string = stringWriter.toString();
        Matcher m = p.matcher(string);
        string = m.replaceAll("");
        return string;
    }

    @SneakyThrows
    public void put(DpsRequest dpsRequest, @NonNull String key, @NonNull Object value) {
        CustomizedMetricsProcessor processor = CustomizedMetricsProcessor.builder(getDpsRedisKey(dpsRequest)).tagsMap(getTsLayerTagMap(dpsRequest, "put")).build();
        processor.process(() -> {
            String jsonValue = JSON.toJSONString(value);
            try {
                stringRedisTemplate.opsForValue().set(key, jsonValue,
                        5 * 60, TimeUnit.SECONDS);
            } catch (Exception e) {
                log.error("Redis put key {} ,error {}", key, getStackTraceAsString(e));
            }
            return null;
        });

    }

    @SneakyThrows
    public void put(DpsRequest dpsRequest, @NonNull final String key, @NonNull final String value, @NonNull final Integer expire) {
        CustomizedMetricsProcessor processor = CustomizedMetricsProcessor.builder(getDpsRedisKey(dpsRequest)).tagsMap(getTsLayerTagMap(dpsRequest, "put")).build();
        processor.process(() -> {
            try {
                putInternal(dpsRequest, key, value, expire);
            } catch (Exception e) {
                log.error("RedisError {}", key);
            }
            return null;
        });

    }


    private void putInternal(DpsRequest dpsRequest, @NonNull final String key, @NonNull final String value, @NonNull final Integer expire) {
        Long ttl = stringRedisTemplate.getExpire(key, TimeUnit.SECONDS);
        if (null != ttl && ttl > 0) {
            stringRedisTemplate.opsForValue().set(key, value, ttl, TimeUnit.SECONDS);
        } else {
            stringRedisTemplate.opsForValue().set(key, value, expire, TimeUnit.SECONDS);
        }
    }


    @SneakyThrows
    public String get(DpsRequest dpsRequest, @NonNull final String key) {
        CustomizedMetricsProcessor processor = CustomizedMetricsProcessor.builder(getDpsRedisKey(dpsRequest)).tagsMap(getTsLayerTagMap(dpsRequest, "get")).build();
        return processor.process(() -> {
            try {
                String res = stringRedisTemplate.opsForValue().get(key);
                if (StringUtils.isEmpty(res)) return "";
                return res;
            } catch (Exception e) {
                log.error("RedisGet key {} ,error {}", key, getStackTraceAsString(e));
                return "";
            }
        });


    }


    @SneakyThrows
    public List<String> multiGet(DpsRequest dpsRequest, Collection<String> keys) {
        CustomizedMetricsProcessor processor = CustomizedMetricsProcessor.builder(getDpsRedisKey(dpsRequest)).tagsMap(getTsLayerTagMap(dpsRequest, "multiGet")).build();
        return processor.process(() -> {
            try {
                return stringRedisTemplate.opsForValue().multiGet(keys);
            } catch (Exception e) {
                log.error("Redis multiGet key {} ,error {}", keys.toString(), getStackTraceAsString(e));
                return Lists.newArrayList();
            }
        });

    }

    @SneakyThrows
    public List<Object> multiGetPip(DpsRequest dpsRequest, Collection<String> keys) {
        CustomizedMetricsProcessor processor = CustomizedMetricsProcessor.builder(getDpsRedisKey(dpsRequest)).tagsMap(getTsLayerTagMap(dpsRequest, "multiGetPip")).build();
        return processor.process(() -> {
            try {
                List<Object> list = this.stringRedisTemplate.executePipelined(new RedisCallback<String>() {
                    @Override
                    public String doInRedis(RedisConnection connection) throws DataAccessException {
                        StringRedisConnection conn = (StringRedisConnection) connection;
                        conn.openPipeline();
                        for (String key : keys) {
                            conn.stringCommands().get(key.getBytes());
                        }
                        return null;
                    }
                });
                return list;
            } catch (Exception e) {
                log.error("Redis multiGetPip key {} ,error {}", keys.toString(), getStackTraceAsString(e));
                return Lists.newArrayList();
            }
        });


    }

    @SneakyThrows
    public List<Object> multiSetPip(DpsRequest dpsRequest, List<String> keys, List<String> vals) {
        CustomizedMetricsProcessor processor = CustomizedMetricsProcessor.builder(getDpsRedisKey(dpsRequest)).tagsMap(getTsLayerTagMap(dpsRequest, "multiSetPip")).build();
        return processor.process(() -> {
            try {
                List<Object> list = this.stringRedisTemplate.executePipelined(new RedisCallback<Boolean>() {
                    @Override
                    public Boolean doInRedis(RedisConnection connection) throws DataAccessException {
                        StringRedisConnection conn = (StringRedisConnection) connection;
                        conn.openPipeline();
                        for (int i = 0; i < keys.size(); i++) {
                            conn.set(keys.get(i), vals.get(i));
                        }
                        return null;
                    }
                });
                return list;
            } catch (Exception e) {
                log.error("Redis multiSetPip key {} ,error {}", keys.toString(), getStackTraceAsString(e));
                return Lists.newArrayList();
            }
        });
    }

    @SneakyThrows
    public List<Object> multiSetPip(DpsRequest dpsRequest, List<String> keys, List<String> vals, long expirationSeconds) {
        CustomizedMetricsProcessor processor = CustomizedMetricsProcessor.builder(getDpsRedisKey(dpsRequest)).tagsMap(getTsLayerTagMap(dpsRequest, "multiSetPip")).build();
        return processor.process(() -> {
            try {
                List<Object> list = this.stringRedisTemplate.executePipelined(new RedisCallback<Boolean>() {
                    @Override
                    public Boolean doInRedis(RedisConnection connection) throws DataAccessException {
                        StringRedisConnection conn = (StringRedisConnection) connection;
                        conn.openPipeline();
                        for (int i = 0; i < keys.size(); i++) {
                            conn.set(keys.get(i), vals.get(i), Expiration.seconds(expirationSeconds), RedisStringCommands.SetOption.UPSERT);
                        }
                        return null;
                    }
                });
                return list;
            } catch (Exception e) {
                log.error("Redis multiSetPip key {} ,error {}", keys.toString(), getStackTraceAsString(e));
                return Lists.newArrayList();
            }
        });

    }

    @SneakyThrows
    public List<Object> multiDelPip(DpsRequest dpsRequest, Collection<String> keys) {
        CustomizedMetricsProcessor processor = CustomizedMetricsProcessor.builder(getDpsRedisKey(dpsRequest)).tagsMap(getTsLayerTagMap(dpsRequest, "multiDelPip")).build();
        return processor.process(() -> {
            try {
                List<Object> list = this.stringRedisTemplate.executePipelined(new RedisCallback<Long>() {
                    @Override
                    public Long doInRedis(RedisConnection connection) throws DataAccessException {
                        StringRedisConnection conn = (StringRedisConnection) connection;
                        for (String key : keys) {
                            conn.del(key);
                        }
                        return null;
                    }
                });
                return list;
            } catch (Exception e) {
                log.error("Redis multiDelPip key {} ,error {}", keys.toString(), getStackTraceAsString(e));
                return Lists.newArrayList();
            }
        });

    }

    @SneakyThrows
    public List<Object> multiHGetPip(DpsRequest dpsRequest, Collection<String> keys) {
        CustomizedMetricsProcessor processor = CustomizedMetricsProcessor.builder(getDpsRedisKey(dpsRequest)).tagsMap(getTsLayerTagMap(dpsRequest, "multiHGetPip")).build();
        return processor.process(() -> {
            try {
                List<Object> list = this.stringRedisTemplate.executePipelined(new RedisCallback<Map<String, String>>() {
                    @Override
                    public Map<String, String> doInRedis(RedisConnection connection) throws DataAccessException {
                        StringRedisConnection conn = (StringRedisConnection) connection;
                        conn.openPipeline();
                        for (String key : keys) {
                            conn.hashCommands().hGetAll(key.getBytes());
                        }
                        return null;
                    }
                });
                return list;
            } catch (Exception e) {
                log.error("Redis multiHGetPip key {} ,error {}", keys.toString(), getStackTraceAsString(e));
                return Lists.newArrayList();
            }
        });

    }


    @SneakyThrows
    public List<Object> multiHGetPip(DpsRequest dpsRequest, String key, Collection<String> keys) {
        CustomizedMetricsProcessor processor = CustomizedMetricsProcessor.builder(getDpsRedisKey(dpsRequest)).tagsMap(getTsLayerTagMap(dpsRequest, "multiHGetPip")).build();
        return processor.process(() -> {
            try {
                List<Object> list = this.stringRedisTemplate.executePipelined(new RedisCallback<String>() {
                    @Override
                    public String doInRedis(RedisConnection connection) throws DataAccessException {
                        StringRedisConnection conn = (StringRedisConnection) connection;
                        conn.openPipeline();
                        for (String field : keys) {
                            conn.hashCommands().hGet(key.getBytes(), field.getBytes());
                        }
                        return null;
                    }
                });
                return list;
            } catch (Exception e) {
                log.error("Redis multiHGetPip key {} ,error {}", keys.toString(), getStackTraceAsString(e));
                return Lists.newArrayList();
            }
        });

    }

    @SneakyThrows
    public void delete(DpsRequest dpsRequest, @NonNull String key) {
        CustomizedMetricsProcessor processor = CustomizedMetricsProcessor.builder(getDpsRedisKey(dpsRequest)).tagsMap(getTsLayerTagMap(dpsRequest, "delete")).build();
        processor.process(() -> {
            try {
                stringRedisTemplate.delete(key);
            } catch (Exception e) {
                log.error("Redis delete key {} ,error {}", key, getStackTraceAsString(e));
            }
            return null;
        });

    }

    @SneakyThrows
    public void hset(DpsRequest dpsRequest, @NonNull final String key, final String field, final String value) {
        CustomizedMetricsProcessor processor = CustomizedMetricsProcessor.builder(getDpsRedisKey(dpsRequest)).tagsMap(getTsLayerTagMap(dpsRequest, "hset")).build();
        processor.process(() -> {
            try {
                stringRedisTemplate.opsForHash().put(key, field, value);
            } catch (Exception e) {
                log.error("Redis hset key {} ,error {}", key, getStackTraceAsString(e));
            }
            return null;
        });

    }

    @SneakyThrows
    public void hashPutAll(DpsRequest dpsRequest, @NonNull final String key, Map<String, String> fieldMap) {
        CustomizedMetricsProcessor processor = CustomizedMetricsProcessor.builder(getDpsRedisKey(dpsRequest)).tagsMap(getTsLayerTagMap(dpsRequest, "hashPutAll")).build();
        processor.process(() -> {
            try {
                stringRedisTemplate.opsForHash().putAll(key, fieldMap);
            } catch (Exception e) {
                log.error("Redis hashPutAll key {} ,error {}", key, getStackTraceAsString(e));
            }
            return null;
        });
    }


    @SneakyThrows
    public void hashPutAll(DpsRequest dpsRequest, @NonNull final String key, Map<String, String> fieldMap, @NonNull final Integer expire) {
        CustomizedMetricsProcessor processor = CustomizedMetricsProcessor.builder(getDpsRedisKey(dpsRequest)).tagsMap(getTsLayerTagMap(dpsRequest, "hashPutAll")).build();
        processor.process(() -> {
            try {
                Long ttl = stringRedisTemplate.getExpire(key, TimeUnit.SECONDS);
                if (null != ttl && ttl > 0) {
                    stringRedisTemplate.opsForHash().putAll(key, fieldMap);
                } else {
                    stringRedisTemplate.opsForHash().putAll(key, fieldMap);
                    stringRedisTemplate.expire(key, expire, TimeUnit.SECONDS);
                }
            } catch (Exception e) {
                log.error("Redis hashPutAll key {} ,error {}", key, getStackTraceAsString(e));
            }
            return null;
        });
    }


    @SneakyThrows
    public String hget(DpsRequest dpsRequest, @NonNull final String key, final String field) {
        CustomizedMetricsProcessor processor = CustomizedMetricsProcessor.builder(getDpsRedisKey(dpsRequest)).tagsMap(getTsLayerTagMap(dpsRequest, "hget")).build();
        return processor.process(() -> {
            try {
                Object value = stringRedisTemplate.opsForHash().get(key, field);
                return Objects.isNull(value) ? null : value.toString();
            } catch (Exception e) {
                log.error("Redis hget key {} ,error {}", key, getStackTraceAsString(e));
                return "";
            }
        });

    }

    @SneakyThrows
    public void hdel(DpsRequest dpsRequest, @NonNull final String key, final String field) {
        CustomizedMetricsProcessor processor = CustomizedMetricsProcessor.builder(getDpsRedisKey(dpsRequest)).tagsMap(getTsLayerTagMap(dpsRequest, "hdel")).build();
        processor.process(() -> {
            try {
                stringRedisTemplate.opsForHash().delete(key, field);
            } catch (Exception e) {
                log.error("Redis hdel key {} ,error {}", key, getStackTraceAsString(e));
            }
            return null;
        });

    }

    @SneakyThrows
    public List<String> hmget(DpsRequest dpsRequest, @NonNull final String key, Collection<Object> fields) {
        CustomizedMetricsProcessor processor = CustomizedMetricsProcessor.builder(getDpsRedisKey(dpsRequest)).tagsMap(getTsLayerTagMap(dpsRequest, "hmget")).build();
        return processor.process(() -> {
            List<String> list = Lists.newArrayList();
            List<Object> values = null;
            try {
                values = stringRedisTemplate.opsForHash().multiGet(key, fields);
            } catch (Exception e) {
                log.error("Redis hmget key {} ,error {}", key, getStackTraceAsString(e));
            }
            if (!CollectionUtils.isEmpty(values)) {
                for (Object obj : values) {
                    list.add(Objects.isNull(obj) ? "" : obj.toString());
                }
            }
            return list;
        });

    }

    @SneakyThrows
    public Map<Object, Object> hgetAll(DpsRequest dpsRequest, @NonNull final String key) {
        CustomizedMetricsProcessor processor = CustomizedMetricsProcessor.builder(getDpsRedisKey(dpsRequest)).tagsMap(getTsLayerTagMap(dpsRequest, "hgetAll")).build();
        return processor.process(() -> {
            try {
                return stringRedisTemplate.opsForHash().entries(key);
            } catch (Exception e) {
                log.error("Redis hgetAl key {} ,error {}", key, getStackTraceAsString(e));
                return Maps.newHashMap();
            }
        });

    }

    @SneakyThrows
    public Map<String, String> hgetAllString(DpsRequest dpsRequest, @NonNull final String key) {
        CustomizedMetricsProcessor processor = CustomizedMetricsProcessor.builder(getDpsRedisKey(dpsRequest)).tagsMap(getTsLayerTagMap(dpsRequest, "hgetAllString")).build();
        return processor.process(() -> {
            Map<Object, Object> map = hgetAll(dpsRequest, key);
            if (CollectionUtils.isEmpty(map)) {
                return Maps.newHashMap();
            }
            Map<String, String> result = Maps.newHashMapWithExpectedSize(map.size());
            for (Map.Entry<Object, Object> entry : map.entrySet()) {
                result.put(entry.getKey().toString(), entry.getValue().toString());
            }
            return result;
        });

    }

    @SneakyThrows
    public Long incr(DpsRequest dpsRequest, final String key) {
        CustomizedMetricsProcessor processor = CustomizedMetricsProcessor.builder(getDpsRedisKey(dpsRequest)).tagsMap(getTsLayerTagMap(dpsRequest, "incr")).build();
        return processor.process(() -> {
            try {
                return stringRedisTemplate.opsForValue().increment(key);
            } catch (Exception e) {
                log.error("Redis incr key {} ,error {}", key, getStackTraceAsString(e));
                return -1L;
            }
        });

    }

    @SneakyThrows
    public Long incr(DpsRequest dpsRequest, final String key, final int seconds) {
        CustomizedMetricsProcessor processor = CustomizedMetricsProcessor.builder(getDpsRedisKey(dpsRequest)).tagsMap(getTsLayerTagMap(dpsRequest, "incr")).build();
        return processor.process(() -> {
            try {
                Long result = stringRedisTemplate.opsForValue().increment(key);
                // 第一次进来，给你一个赋值
                if (result != null && result.equals(1L)) {
                    stringRedisTemplate.expire(key, seconds, TimeUnit.SECONDS);
                }
                return result;
            } catch (Exception e) {
                log.error("Redis incr key {} ,error {}", key, getStackTraceAsString(e));
                return -1L;
            }
        });
    }

    @SneakyThrows
    public Long pfadd(DpsRequest dpsRequest, final String key, final String... elements) {
        CustomizedMetricsProcessor processor = CustomizedMetricsProcessor.builder(getDpsRedisKey(dpsRequest)).tagsMap(getTsLayerTagMap(dpsRequest, "pfadd")).build();
        return processor.process(() -> {
            try {
                return stringRedisTemplate.opsForHyperLogLog().add(key, elements);
            } catch (Exception e) {
                log.error("Redis pfadd key {} ,error {}", key, getStackTraceAsString(e));
                return -1L;
            }
        });

    }

    @SneakyThrows
    public Long pfcount(DpsRequest dpsRequest, final String key) {
        CustomizedMetricsProcessor processor = CustomizedMetricsProcessor.builder(getDpsRedisKey(dpsRequest)).tagsMap(getTsLayerTagMap(dpsRequest, "pfcount")).build();
        return processor.process(() -> {
            // pfcount 非精准统计 key的计数
            try {
                return stringRedisTemplate.opsForHyperLogLog().size(key);
            } catch (Exception e) {
                log.error("Redis pfcount key {} ,error {}", key, getStackTraceAsString(e));
                return -1L;
            }
        });

    }

    @SneakyThrows
    public Boolean expire(DpsRequest dpsRequest, final String key, final int seconds) {
        CustomizedMetricsProcessor processor = CustomizedMetricsProcessor.builder(getDpsRedisKey(dpsRequest)).tagsMap(getTsLayerTagMap(dpsRequest, "expire")).build();
        return processor.process(() -> {
            try {
                return stringRedisTemplate.expire(key, seconds, TimeUnit.SECONDS);
            } catch (Exception e) {
                log.error("Redis expire key {} ,error {}", key, getStackTraceAsString(e));
                return false;
            }
        });

    }

    @SneakyThrows
    public Long getListSize(DpsRequest dpsRequest, @NonNull final String key) {
        CustomizedMetricsProcessor processor = CustomizedMetricsProcessor.builder(getDpsRedisKey(dpsRequest)).tagsMap(getTsLayerTagMap(dpsRequest, "getListSize")).build();
        return processor.process(() -> {
            try {
                return stringRedisTemplate.opsForList().size(key);
            } catch (Exception e) {
                log.error("RedisGet key {} ,error {}", key, getStackTraceAsString(e));
                return -1L;
            }
        });

    }

    @SneakyThrows
    public List<String> lrange(DpsRequest dpsRequest, @NonNull final String key, final long index, final long size) {
        CustomizedMetricsProcessor processor = CustomizedMetricsProcessor.builder(getDpsRedisKey(dpsRequest)).tagsMap(getTsLayerTagMap(dpsRequest, "lrange")).build();
        return processor.process(() -> {
            try {
                return stringRedisTemplate.opsForList().range(key, index, size);
            } catch (Exception e) {
                log.error("RedisGet key {} ,error {}", key, getStackTraceAsString(e));
                return Lists.newArrayList();
            }
        });

    }
}
